#A first example
#converting the result of a function f 
#into the unicode version.
def escape_unicode(f):
    def wrap(*args, **kwargs):
        x = f(*args, **kwargs)
        return ascii(x)
    return wrap

#Create a function
def northern_city():
    return 'Tromsø'

#and test it
print(northern_city())

#redefine it with the decoration
@escape_unicode
def northern_city():
    return 'Tromsø'

#and test it
print(northern_city())


#Validating arguments
# The following is a decorator factory: it returns decorators
def check_non_negative(index):
    # This is the actual decorator
    def validator(f):
        # This is the wrapper function
        def wrap(*args):
             if args[index] < 0:
                raise ValueError('Argument {} must be non-negative.'
                                 .format(index))
             return f(*args)
        return wrap
    return validator

#Using decorator
@check_non_negative(1) #technically this is not the decorator, it's a function that returns the 'validator' decorator
def create_list(value, size):
     return [value] * size

create_list('a', 3)
create_list(123, -6)

#Classes as decorators
#In this example, we’ll create a decorator class CallCount which keeps track of how many times it’s called:

class CallCount:
    def __init__(self, f):
        self.f = f
        self.count = 0
    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.f(*args, **kwargs)

@CallCount
def hello(name):
    print('Hello, {}!'.format(name))

hello('Fred')
hello('Wilma')
hello('Betty')
hello('Barney')
hello.count


#Instances as decorators
#A decorator which can be turned on or off and prints some information each time the decorated function is called. 
class Trace:
    def __init__(self):
        self.enabled = True

    def __call__(self, f):
        def wrap(*args, **kwargs):
            if self.enabled:
                print('Calling {}'.format(f))
            return f(*args, **kwargs)
        return wrap

tracer = Trace()
@tracer
def rotate_list(l):
    return l[1:] + [l[0]]

l = [1, 2, 3]
l = rotate_list(l)
l
l = rotate_list(l)
l
l = rotate_list(l)
l

tracer.enabled = False
l = rotate_list(l)
l
l = rotate_list(l)
l
l = rotate_list(l)
l


#Multiple decorators
#For code details see island_maker.py
from island_maker import norwegian_island_maker
norwegian_island_maker('Llama')
norwegian_island_maker('Python')
norwegian_island_maker('Troll')

from island_maker import tracer
tracer.enabled = False
norwegian_island_maker('Llama')
norwegian_island_maker('Python')
norwegian_island_maker('Troll')


#Decorating methods
class IslandMaker:
    def __init__(self, suffix):
        self.suffix = suffix

    @tracer
    def make_island(self, name):
        return name + self.suffix

im = IslandMaker(' Island')
im.make_island('Python')
im.make_island('Llama')

#if it doesn't trace successfully 
tracer.enabled=True
#then try again.


#Decorators and function metadata
def hello():
    "Print a well-known message."
    print('Hello, world!')

hello.__name__
hello.__doc__
help(hello)

#define a simple no-op decorator
def noop(f):
    def noop_wrapper():
        return f()
    return noop_wrapper

#decorate our hello function:
@noop
def hello():
    "Print a well-known message."

print('hello world!')
help(hello)
hello.__name__
hello.__doc__

#Manually updating decorator metadata
def noop(f):
    def noop_wrapper():
        return f()
    noop_wrapper.__name__ = f.__name__
    noop_wrapper.__doc__ = f.__doc__
    return noop_wrapper

@noop
def hello():
    "Print a well-known message."

help(hello)

#Updating decorator metadata with functools.wraps
import functools
def noop(f):
    @functools.wraps(f)
    def noop_wrapper():
        return f()
    return noop_wrapper

@noop
def hello():
    "Print a well-known message."

help(hello)
hello.__name__
hello.__doc__